/**
 * Kernel Monkeys
 * Andrew Boguk
 * Sergiy Kulyk
 * Nicholas Swartzendruber
 */

/*
 * Scheduler ADT that handles context switches and semaphores.
 *
 * @Author: Andrew Boguk
 */

#include <stdio.h>
#include "sched.h"
#include "cpu.h"
#include "queue.h"

/*
 * Creates and initializes scheduler.
 *
 * @param ready_queue : Queue full of PCB structs.
 * @return : Scheduler ADT used with the other functions.
 */
Scheduler sched_create(Queue ready_queue)
{
	Scheduler sched;
	sched = (Scheduler)malloc(sizeof(struct Sched_Struct));

	sched->ready = ready_queue;
	sched->blocked = queue_CreateQueue(MAX_ELEMENTS);
	sched->running = queue_FrontAndDequeue(ready_queue);
	sched->locked = queue_CreateQueue(MAX_ELEMENTS);

	return sched;
}

/*
 * Does the context switch depending on what type of interrupt
 * gets passed into the function.
 *
 * Currently the function is not modular for simplicity sake, will
 * make more flexible once things come together.
 *
 * @param sched : Scheduler ADT
 * @param type : The type of interrupt that is triggering the context switch.
 */
void sched_switch(Scheduler sched, int type)
{
	PCBRecord temp;

	switch (type)
	{
	case INT_TIMER:
		sched->running->state = STATE_READY;

		queue_Enqueue(sched->running, sched->ready);
		sched->running = queue_FrontAndDequeue(sched->ready);

		sched->running->state = STATE_RUNNING;

		break;
	case INT_IO:
		temp = queue_FrontAndDequeue(sched->blocked);
		temp->state = STATE_READY;

		queue_Enqueue(temp, sched->ready);

		break;
	case INT_IO_REQUEST:
		sched->running->state = STATE_BLOCKED;

		queue_Enqueue(sched->running, sched->blocked);
		sched->running = queue_FrontAndDequeue(sched->ready);

		sched->running->state = STATE_RUNNING;

		break;
	}
}

/*
 * Returns the current PCB struct set as running.
 *
 * @param sched : Scheduler ADT
 * @return PCB : Currently running PCB
 */
PCBRecord sched_getRunning(Scheduler sched)
{
	return sched->running;
}

/**
 * Asks for lock of the semaphore, throws into locked queue
 * if access denied.
 *
 * @param sched : Scheduler ADT
 */
void sched_getLock(Scheduler sched)
{
	if (sched->semaphore != SEM_UNLOCKED &&
		sched->semaphore != sched->running->pid)
	{
		sched->running->state = STATE_BLOCKED;
		sched->running->waiting_on = BLOCKED_SEM;

		queue_Enqueue(sched->running, sched->locked);

		sched->running = queue_FrontAndDequeue(sched->ready);

		sched->running->state = STATE_RUNNING;
		sched->running->waiting_on = BLOCKED_NONE;

		return;
	}

	sched->semaphore = sched->running->pid;
	sched->running->owns = sched->running->pid;
}

/**
 * Release lock on semaphore and switch running process to
 * the next in locked queue.
 *
 * @param sched : Scheduler ADT
 */
void sched_releaseLock(Scheduler sched)
{
	if (sched->semaphore == sched->running->pid)
	{
		//Release ownership.
		sched->running->owns = SEM_UNLOCKED;
		sched->semaphore = SEM_UNLOCKED;

		if (queue_IsEmpty(sched->locked) == TRUE)
			return;

		//First in locked queue gets semaphore, puts process into ready queue.
		PCBRecord temp = queue_FrontAndDequeue(sched->locked);
		sched->semaphore = temp->pid;
		temp->owns = temp->pid;
		temp->state = STATE_READY;
		temp->waiting_on = BLOCKED_NONE;

		queue_Enqueue(temp, sched->ready);
	}
}
